package xyz.zhzby.cyzx.judge.config;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.zhzby.cyzx.judge.core.Dispatcher;
import xyz.zhzby.cyzx.judge.entity.JudgeResult;
import xyz.zhzby.cyzx.judge.messenger.MessageSender;
import xyz.zhzby.cyzx.judge.service.IJudgeResultService;
import xyz.zhzby.cyzx.judge.service.ISubmissionService;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zby
 * @date 2022/02/04
 * @Description 应用程序调度器.
 */
@Component
@Slf4j
public class ApplicationDispatcher {

    @Autowired
    private Dispatcher judgerDispatcher;

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private ISubmissionService submissionService;

    @Autowired
    private IJudgeResultService judgeResultService;

    /**
     * @Param [submissionId] [评测记录的ID]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 收到消息队列的新的评测请求时的回调函数.
     */
    public void onSubmissionCreated(Long submissionId) {
        try {
            judgerDispatcher.createNewSubmissionTask(submissionId);
        } catch (Exception ex) {
            log.error(ex.getMessage());
        }
    }

    /**
     * @Param [submissionId] [评测记录的唯一标识符]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 当系统错误发生时通知用户.
     */
    public void onErrorOccurred(Long submissionId) {
        submissionService.updateSubmission(submissionId, 0, 0, 0, "SE", "Internal error occured.");

        Map<String, Object> mapMessage = new HashMap<>();
        mapMessage.put("event", "ErrorOccurred");
        mapMessage.put("submissionId", submissionId);

        messageSender.sendMessage(mapMessage);
    }

    /**
     * @Param [submissionId, result] [评测记录的ID]
     * @Return void [编译结果]
     * @Author zby
     * @Date 2022/02/04
     * @Description 当编译阶段结束时通知用户.
     */
    public void onCompileFinished(Long submissionId, Map<String, Object> result) {
        boolean isSuccessful = (Boolean) result.get("isSuccessful");
        String log = getJudgeLog(result);

        if (!isSuccessful) {
            submissionService.updateSubmission(submissionId, 0, 0, 0, "CE", log);
        }
        Map<String, Object> mapMessage = new HashMap<>();
        mapMessage.put("event", "CompileFinished");
        mapMessage.put("submissionId", submissionId);
        mapMessage.put("isSuccessful", isSuccessful);
        mapMessage.put("log", log);

        messageSender.sendMessage(mapMessage);
    }

    /**
     * @Param [submissionId, checkpointId, runtimeResult] [提交记录的ID, 测试点的编号, 某个测试点的程序运行结果]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 实时返回评测结果.
     */
    public void onOneTestPointFinished(Long submissionId, Long checkpointId, Map<String, Object> runtimeResult) {
        String runtimeResultSlug = getRuntimeResultSlug(runtimeResult);
        String runtimeResultName = getRuntimeResultName(runtimeResultSlug);
        int usedTime = getUsedTime(runtimeResult);
        int usedMemory = getUsedMemory(runtimeResult);
        int score = getScore(runtimeResult);

        Map<String, Object> mapMessage = new HashMap<>();
        mapMessage.put("event", "TestPointFinished");
        mapMessage.put("submissionId", submissionId);
        mapMessage.put("checkpointId", checkpointId);
        mapMessage.put("runtimeResult", runtimeResultName);
        mapMessage.put("usedTime", usedTime);
        mapMessage.put("usedMemory", usedMemory);
        mapMessage.put("score", score);

        messageSender.sendMessage(mapMessage);
    }

    /**
     * @Param [submissionId, runtimeResults] [提交记录的ID, 对各个测试点的评测结果集]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 持久化程序评测结果
     */
    public void onAllTestPointsFinished(Long submissionId, List<Map<String, Object>> runtimeResults) {
        int totalTime = 0;
        int maxMemory = 0;
        int totalScore = 0;
        String runtimeResultSlug = "AC";
        String log;

        for (Map<String, Object> runtimeResult : runtimeResults) {
            String currentRuntimeResultSlug = getRuntimeResultSlug(runtimeResult);
            int usedTime = getUsedTime(runtimeResult);
            int usedMemory = getUsedMemory(runtimeResult);
            int score = getScore(runtimeResult);

            totalTime += usedTime;
            if (usedMemory > maxMemory) {
                maxMemory = usedMemory;
            }
            if ("AC".equals(currentRuntimeResultSlug)) {
                totalScore += score;
            }
            if (!"AC".equals(currentRuntimeResultSlug)) {
                runtimeResultSlug = currentRuntimeResultSlug;
            }
        }
        log = getJudgeLog(runtimeResults, runtimeResultSlug, totalTime, maxMemory, totalScore);
        submissionService.updateSubmission(submissionId, totalTime, maxMemory, totalScore, runtimeResultSlug, log);

        Map<String, Object> mapMessage = new HashMap<>();
        mapMessage.put("event", "AllTestPointsFinished");
        mapMessage.put("submissionId", submissionId);
        mapMessage.put("runtimeResult", getRuntimeResultName(runtimeResultSlug));
        mapMessage.put("totalTime", totalTime);
        mapMessage.put("maxMemory", maxMemory);
        mapMessage.put("totalScore", totalScore);

        messageSender.sendMessage(mapMessage);
    }

    /**
     * @Param [testId, runtimeResult] [提交记录的ID, 测试的程序运行结果]
     * @Return void []
     * @Author zby
     * @Date 2022/2/4
     * @Description 持久化程序测试结果
     */
    public void onTestFinished(Long submissionId, Map<String, Object> runtimeResult) {
        String runtimeResultSlug = getRuntimeResultSlug(runtimeResult);
        String runtimeResultName = getRuntimeResultName(runtimeResultSlug);
        int usedTime = getUsedTime(runtimeResult);
        int usedMemory = getUsedMemory(runtimeResult);

        String log = getJudgeLog(usedTime, usedMemory);
        submissionService.updateSubmission(submissionId, usedTime, usedMemory, log);

        String runtimeOutput = getRuntimeOutput(runtimeResult);

        Map<String, Object> mapMessage = new HashMap<>();
        mapMessage.put("event", "TestFinished");
        mapMessage.put("submissionId", submissionId);
        mapMessage.put("runtimeResult", runtimeResultName);
        mapMessage.put("usedTime", usedTime);
        mapMessage.put("usedMemory", usedMemory);
        mapMessage.put("runtimeOutput", runtimeOutput);

        messageSender.sendMessage(mapMessage);
    }

    /**
     * @Param [runtimeResult] [程序评测结果]
     * @Return java.lang.String [程序评测结果的唯一英文缩写]
     * @Author zby
     * @Date 2022/02/04
     * @Description 从评测结果集中获取程序评测结果的唯一英文缩写.
     */
    private String getRuntimeResultSlug(Map<String, Object> runtimeResult) {
        Object runtimeResultObject = runtimeResult.get("runtimeResult");

        if (runtimeResultObject == null) {
            return "SE";
        }
        return (String) runtimeResultObject;
    }

    /**
     * @Param [runtimeResultSlug] [评测结果的唯一英文缩写]
     * @Return java.lang.String [评测结果的全称]
     * @Author zby
     * @Date 2022/02/04
     * @Description 获取评测结果的全称.
     */
    private String getRuntimeResultName(String runtimeResultSlug) {
        LambdaQueryWrapper<JudgeResult> qw = new LambdaQueryWrapper<>();
        qw.eq(JudgeResult::getSlug, runtimeResultSlug);
        JudgeResult judgeResult = judgeResultService.getOne(qw);

        if (judgeResult == null) {
            return "System Error";
        }
        return judgeResult.getName();
    }

    /**
     * @Param [runtimeResult] [程序评测结果]
     * @Return int [程序运行时间(ms)]
     * @Author zby
     * @Date 2022/02/04
     * @Description 从评测结果集中获取程序运行时间(ms).
     */
    private int getUsedTime(Map<String, Object> runtimeResult) {
        Object usedTimeObject = runtimeResult.get("usedTime");

        if (usedTimeObject == null) {
            return 0;
        }
        return (Integer) usedTimeObject;
    }

    /**
     * @Param [runtimeResult] [程序评测结果]
     * @Return int [内存使用量(KB)]
     * @Author zby
     * @Date 2022/02/04
     * @Description 从评测结果集中获取内存使用量(KB).
     */
    private int getUsedMemory(Map<String, Object> runtimeResult) {
        Object usedMemoryObject = runtimeResult.get("usedMemory");

        if (usedMemoryObject == null) {
            return 0;
        }
        return (Integer) usedMemoryObject;
    }

    /**
     * @Param [runtimeResult] [程序评测结果]
     * @Return int [测试点对应的分值]
     * @Author zby
     * @Date 2022/02/04
     * @Description 从评测结果集中获取测试点对应的分值
     */
    private int getScore(Map<String, Object> runtimeResult) {
        Object scoreObject = runtimeResult.get("score");

        if (scoreObject == null) {
            return 0;
        }
        return (Integer) scoreObject;
    }

    /**
     * @Param [result] [包含编译状态的Map<String, Object>对象]
     * @Return java.lang.String [格式化后的日志]
     * @Author zby
     * @Date 2022/02/04
     * @Description 格式化编译时日志.
     */
    private String getJudgeLog(Map<String, Object> result) {
        boolean isSuccessful = (Boolean) result.get("isSuccessful");
        String compileLog = (String) result.get("log");

        StringBuilder formatedLogBuilder = new StringBuilder();
        formatedLogBuilder.append(
                String.format("Compile %s.\n\n", isSuccessful ? "Successful" : "Error"));
        if (!isSuccessful) {
            //todo 改到Linux后要切换这个
//            formatedLogBuilder.append(compileLog.replaceAll("(File \"d:|d:)[\\S]{0,40}.(java:|cpp:|c:|py\",)", "")
//                    .replaceAll("\\n[ ]*", "\n").replaceAll("\\n\\n", "\n"));
            formatedLogBuilder.append(compileLog.replaceAll("(File \"/root/cyzx/judger|/root/cyzx/judger)[\\S]{0,40}.(java:|cpp:|c:|py\",)", "")
                    .replaceAll("\\n[ ]*", "\n").replaceAll("\\n\\n", "\n"));
            formatedLogBuilder.append("\nCompile Error, Time = 0 ms, Memory = 0 KB, Score = 0.\n");
        }
        return formatedLogBuilder.toString();
    }

    /**
     * @Param [runtimeResults, runtimeResultSlug, totalTime, maxMemory, totalScore]
     * @Param [对各个测试点的评测结果集]
     * @Return java.lang.String []
     * @Author zby
     * @Date 2022/02/04
     * @Description 格式化运行时日志.
     */
    private String getJudgeLog(List<Map<String, Object>> runtimeResults, String runtimeResultSlug, int totalTime, int maxMemory, int totalScore) {
        int checkpointId = -1;
        String runtimeResultName = getRuntimeResultName(runtimeResultSlug);

        StringBuilder formatedLogBuilder = new StringBuilder();
        formatedLogBuilder.append("Compile Successfully.\n\n");
        for (Map<String, Object> runtimeResult : runtimeResults) {
            String currentRuntimeResultSlug = getRuntimeResultSlug(runtimeResult);
            String currentRuntimeResultName = getRuntimeResultName(currentRuntimeResultSlug);
            int usedTime = getUsedTime(runtimeResult);
            int usedMemory = getUsedMemory(runtimeResult);
            int score = getScore(runtimeResult);

            if (!"AC".equals(currentRuntimeResultSlug)) {
                score = 0;
            }
            formatedLogBuilder.append(String.format("- Test Point #%d: %s, Time = %d ms, Memory = %d KB, Score = %d\n",
                    ++checkpointId, currentRuntimeResultName, usedTime, usedMemory, score));
        }
        formatedLogBuilder.append(String.format("\n%s, Time = %d ms, Memory = %d KB, Score = %d\n",
                runtimeResultName, totalTime, maxMemory, totalScore));
        return formatedLogBuilder.toString();
    }


    /**
     * @Param [runtimeResults, runtimeResultSlug, totalTime, maxMemory, totalScore]
     * @Param [对各个测试点的评测结果集]
     * @Return java.lang.String []
     * @Author zby
     * @Date 2022/02/04
     * @Description 格式化运行时日志.
     */
    private String getJudgeLog(int usedTime, int usedMemory) {

        return "Compile Successfully.\n\n" +
                String.format("Time = %d ms, Memory = %d KB\n",
                        usedTime, usedMemory);
    }

    /**
     * @Param [result] [包含编译状态的Map<String, Object>对象]
     * @Return java.lang.String [格式化后的输出]
     * @Author zby
     * @Date 2022/02/04
     * @Description 格式化编运行后输出
     */
    private String getRuntimeOutput(Map<String, Object> result) {
        String runtimeOutput = (String) result.get("runtimeOutput");
        if (runtimeOutput == null) {
            runtimeOutput = "";
        }
        return runtimeOutput;
    }

}
